﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="EnsureContentType.cs" company="Collaboris Ltd.">
//   Copyright (c) Collaboris Ltd. All rights Reserved.
// </copyright>
// <summary>
//   Creates or Updates a Content Type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace Collaboris.Saf.Actions.Wss.ContentType
{
    using System;
    using System.Collections.Generic;
    using System.Xml;

    using Entities;
    using Engine;

    using SharePoint.Wss.Attributes;

    using Sharepoint.WSS.ContentTypes;
    using Utils;

    using Microsoft.SharePoint;

   [CollaborisClass(
        Title = "Ensure Content Type",
        ReleaseStatus = ReleaseStatus.Stable,
        DisableDocumentation = false,
        ContactAuthorEmail = "Support@Collaboris.co.uk",
        Summary = "Allows a content type to be added or updated in a SharePoint site." ,
        Remarks = "<p>This action will : </p>" +
                    @"<ul><li>Add a new content type if it doesn't exist.</li>" + 
                    @"<li>Amend a content type if it already exists.</li>" + 
                    @"<li>Optionally push the changes down to dependant content types.</li></ul>" +
                    @"The action uses the same CAML (Elements) file that ships with SharePoint." +
                    @"<p>If the content type already exists, then certain ""FieldRef"" properties will <b>not</b> " +
                    @"be updated. The following list details the properties that can be updated for any " +
                    @"given ""FieldRef"".</P>" +
                    @"<ul><li>DisplayName</li>" +
                    @"<li>Hidden</li>" +
                    @"<li>Required</li>" +
                    @"<li>Sealed</li></ul>" +
                    @"If any other 'FieldRef' properties are supplied then they will be ignored.<br/>Resource Folders are <b>not</b> catered for.",
        UndoImplemented = true,
        ClassGuid = "{156E17A5-A7BA-4e9f-8A48-1BC341A7C640}",
        SupportFiles = new string[] { @"ContentType\Elements.xml" }
    )
    ]
    public class EnsureContentType : WSSActionBase<ElementsInfo>
    {
        /// <summary>
        /// Contains the CAML Elements file.
        /// </summary>
        private XmlDocument camlDoc;

        /// <summary>
        /// When called moves the files from the originating location
        /// to the target location.
        /// </summary>
        /// <param name="context">
        /// </param>
        /// <param name="entity">
        /// The entity.
        /// </param>
        /// <returns>
        /// </returns>
        protected override ProcessStatus DoAction(SafContext context, ElementsInfo entity)
        {
            SaveOldContentTypes(entity);
            ProcessStatus status = this.RunEnsureContentType(entity, context);
         
            this.Trace.TraceMethodEnd("ProcessAction()");

            return status;
        }

        /// <summary>
        /// Deletes(Partial UNDO) all the unused contenttypes specified in the macro.xml.
        /// </summary>
        /// <param name="context">
        /// </param>
        /// <param name="entity">
        /// The entity.
        /// </param>
        /// <returns>Always returns ProcessStatus as "Success" because it does the partial undo.
        /// </returns>
        protected override ProcessStatus UndoAction(SafContext context, ElementsInfo entity)
        {
            ProcessStatus status = this.UndoContentTypes(entity, context);

            this.Trace.TraceMethodEnd("UndoAction()");

            return status;            
        }

        /// <summary>
        /// Checks the Data entity used by the Action for Validity
        /// </summary>
        /// <param name="context">The <see cref="SafContext"/> passed in by the SAF Engine.</param>
        /// <param name="entity">The data entity to Validate.</param>
        /// <param name="errors">List of <see cref="ValidationError"/>s</param>
        /// <param name="mode"><see cref="ProcessMode"/>signalling wheter we are Doing, or Undoing the action.</param>
        /// <returns>false if invalid; otherwise true.</returns>
        protected override bool IsValid(SafContext context, ElementsInfo entity, ref List<ValidationError> errors, ProcessMode mode)
        {
            int currentCount = errors.Count;

            // Validate each object now.
            ValidateStringForNull(entity.FilePath, "ElementsInfo.FilePath", ref errors);
            ValidateNullable(entity.PushChangesToList, "ElementsInfo.PushChangesToList", ref errors);
            ValidateNullable(entity.RemoveInChild, "ElementsInfo.RemoveInChild", ref errors);

            // read in the Content Type
            this.camlDoc = new XmlDocument();
            this.camlDoc.Load(entity.FilePath);


            // TODO: Can we get hold of the schema here and validate ?            

            // Have we added errors ?
            return errors.Count == currentCount;
        }



        /// <summary>
        /// Runs the type of the ensure content.
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="context">The context.</param>
        /// <returns>ProcessStatus signifying the success, or failure.</returns>
        private ProcessStatus RunEnsureContentType(ElementsInfo entity, SafContext context)
        {
            this.Trace.TraceMethodStart("RunEnsureContentType");

            ProcessStatus status;

            // open the file with CAML in it 
            this.Trace.TraceVerbose("Starting to Parse {0}", entity.FilePath);

            // TODO: ContinueOnError ?
            bool rc = SPContentTypeHelper.SaveContentType(
                    TargetWeb, 
                    this.camlDoc,
                    entity.RemoveInChild.Value, 
                    this.StopOnDoException, 
                    entity.PushChangesToList.Value);
            
            if (rc)
                status = ProcessStatus.Success;
            else
                status = ProcessStatus.Warning;

            TargetWeb.Update(); 
            
            this.Trace.TraceMethodEnd("RunEnsureContentType");

            return status;
        }

        /// <summary>
        /// Saves the old content types to a Serializable Dictionary
        /// in the webs Property Bag.
        /// </summary>
        /// <param name="entity">The entity.</param>
        private void SaveOldContentTypes(ElementsInfo entity)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "EnsureContentType", "SaveOldContentTypes");

            SerializableStringDictionary oldTypes = new SerializableStringDictionary();

            TraceHelper.TraceVerbose(TraceSwitch, "SPContentTypeHelper", "Selecting Content Types from the CAML Xml Document");

            List<SPContentTypeId> contentTypesFromCamlDoc = this.GetContentTypesFromCamlDocument(entity);
            if (contentTypesFromCamlDoc == null)
                return;

            // lets loop through all the Content Types in the CAML File...
            foreach (SPContentTypeId contentTypeIdFromCamlDoc in contentTypesFromCamlDoc)
            {
                if (TargetWeb.ContentTypes[contentTypeIdFromCamlDoc] != null)
                {
                    try
                    {
                        SPContentType contentType = SPContentTypeHelper.GetContentType(TargetWeb, contentTypeIdFromCamlDoc);
                        if (contentType != null)
                        {
                            TraceHelper.TraceVerbose(
                                    TraceSwitch,
                                    "SPContentTypeHelper",
                                    "Found this Content Type '{0}' - saving to the Webs Property Bag.",
                                    contentType.Name);

                            oldTypes.Add(contentTypeIdFromCamlDoc.ToString(), SPContentTypeHelper.GetSchema(contentType));
                        }
                    }
                    catch (Exception ex)
                    {
                        if (this.StopOnDoException)
                        {
                            // bubble it back out and let the end of the thread
                            // log the exception.
                            TraceHelper.TraceMethodEnd(TraceSwitch, "EnsureContentType", "SaveOldContentTypes");
                            throw;
                        }

                        TraceHelper.TraceWarning(
                                TraceSwitch,
                                "SPContentTypeHelper",
                                "There was a problem saving the old Content Type '{0}' into the Property Bag",
                                contentTypeIdFromCamlDoc.ToString());

                        TraceHelper.TraceException(TraceSwitch, "SPContentTypeHelper", "UndoContentTypes", ex);
                    }
                    finally
                    {
                        if (oldTypes.Count > 0)
                            this.SaveProperty(TargetWeb, this.GetUniqueKey(entity), oldTypes);
                        TraceHelper.TraceMethodEnd(TraceSwitch, "EnsureContentType", "SaveOldContentTypes");
                    }
                }
            }
        }

        /// <summary>
        /// Deletes the unused contentTypes specified in the macro.xml
        /// </summary>
        /// <param name="entity">The entity.</param>
        /// <param name="context">The context.</param>
        /// <returns>ProcessStatus signifying the success, or failure.</returns>
        private ProcessStatus UndoContentTypes(ElementsInfo entity, SafContext context)
        {
            TraceHelper.TraceMethodStart(TraceSwitch, "EnsureContentType", "UndoContentTypes");

            ProcessStatus status = ProcessStatus.Success;

            // open the file with CAML in it 
            this.Trace.TraceVerbose("Starting to Parse {0}", entity.FilePath);

           SerializableStringDictionary oldTypesFromPropBag = this.GetProperty<SerializableStringDictionary>(
                    TargetWeb, this.GetUniqueKey(entity));

            if (oldTypesFromPropBag == null)
                oldTypesFromPropBag = new SerializableStringDictionary();

            List<SPContentTypeId> contentTypesFromCamlDoc = this.GetContentTypesFromCamlDocument(entity);
            if (contentTypesFromCamlDoc == null)
                return ProcessStatus.Warning;

            // lets loop through all the Content Types in the CAML File...
            foreach (SPContentTypeId contentTypeIdFromCamlDoc in contentTypesFromCamlDoc)
            {
                if (TargetWeb.ContentTypes[contentTypeIdFromCamlDoc] != null)
                {
                    // Does the Propery Bag contain the Content Type CAML ?
                    if (oldTypesFromPropBag.ContainsKey(contentTypeIdFromCamlDoc.ToString()))
                    {
                        string oldCaml = oldTypesFromPropBag[contentTypeIdFromCamlDoc.ToString()];

                        if (string.IsNullOrEmpty(oldCaml))
                        {
                            TraceHelper.TraceWarning(
                                    TraceSwitch,
                                    "EnsureContentType",
                                    "This Content Type '{0}' exists in the CAML file '{1}' but no old CAML can be found.",
                                    contentTypeIdFromCamlDoc.ToString(),
                                    entity.FilePath);

                            status = ProcessStatus.Warning;
                        }
                        else
                        {
                            TraceHelper.TraceVerbose(
                                    TraceSwitch,
                                    "EnsureContentType",
                                    "Reverting this Content Type '{0}' back to this CAML '{1}'",
                                    contentTypeIdFromCamlDoc.ToString(),
                                    oldCaml);

                            SPContentType ctype = SPContentTypeHelper.GetContentTypeFromAll(TargetWeb, contentTypeIdFromCamlDoc);
                            SPContentTypeHelper.UpdateContentType(
                                    ctype, TargetWeb, oldCaml, entity.PushChangesToList.Value, entity.RemoveInChild.Value);
                        }
                    }
                    else
                    {
                        // theres no CAML Doc in the Prop Bag, so it was new - so time to try and delete it.
                        TraceHelper.TraceVerbose(
                                TraceSwitch,
                                "EnsureContentType",
                                "Deleting this Content Type '{0}' as it doesn't exist in this CAML file '{1}'",
                                contentTypeIdFromCamlDoc.ToString(),
                                entity.FilePath);

                        SPContentTypeHelper.DeleteContentType(TargetWeb, contentTypeIdFromCamlDoc);
                    }
                }
            }

            TargetWeb.Update();

            TraceHelper.TraceMethodEnd(TraceSwitch, "EnsureContentType", "UndoContentTypes");

            return status;
        }

        private List<SPContentTypeId> GetContentTypesFromCamlDocument(ElementsInfo entity)
        {
            List<SPContentTypeId> contentTypesFromCamlDoc  = SPContentTypeHelper.GetContentTypesFromCamlDoc(this.camlDoc);
            if (contentTypesFromCamlDoc == null)
            {
                TraceHelper.TraceWarning(
                        TraceSwitch,
                        "EnsureContentType",
                        "No Content Types can be found from this CAML File '{0}'.",
                        entity.FilePath);

                return null;
            }

            return contentTypesFromCamlDoc;
        }
    }
}
